package zdream.pmfield.test.stage.effect;

import org.junit.Before;
import org.junit.Test;

import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.Timeline;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.action.BuffAction;
import zdream.pmfield.stage.message.ActionFinishedMessage;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.FoeAddBuffMessage;
import zdream.pmfield.stage.message.FoeBStatLevelChangeMessage;
import zdream.pmfield.stage.message.FoeReceiveDamageMessage;
import zdream.pmfield.stage.message.FoeRemoveBuffMessage;
import zdream.pmfield.stage.message.TimelineOrderChangeMessage;
import zdream.pmfield.stage.message.TurnFinishedMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-12-20
 * @since 0.0.1
 */
public class ChargingEffectCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testChargingEffect() {
		MoveTemplate chargingMove = storage.findMoveById(10000150, 1);
		assertEquals("蓄力攻击", chargingMove.name);
		assertTrue(chargingMove.hasTags(StageConstant.TAG_CHARGING));
		assertEquals(MoveTemplate.COST_ALL, chargingMove.actionPoint);
		assertEquals(MoveTemplate.COST_ALL, chargingMove.movePoint);

		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(chargingMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.BUFF_CHARGING));
			assertTrue(message.success);
			return true;
		});

		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);
		wrapper.runUntil(FoeRemoveBuffMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.BUFF_CHARGING));
			return true;
		});
	}

	@Test
	public void chargingReleaseTimeUpdatedWhenFoeSpeedChanged() {
		MoveTemplate chargingMove = storage.findMoveById(10000150, 1);
		assertEquals("蓄力攻击", chargingMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);
		MoveTemplate speedUpMove = storage.findMoveById(10000034, 1);
		assertEquals("对方速度上升", speedUpMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(114))
				.withLevel(30)
				.withMove(chargingMove)
				.skipShuffleVStat()
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(123))
				.withLevel(30)
				.withMove(normalMove)
				.withMove(speedUpMove)
				.skipShuffleVStat()
				.build();
		assertTrue(foe2.getBStat(BStat.SP) > foe1.getBStat(BStat.SP));

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.BUFF_CHARGING));
			assertTrue(message.success);
			return true;
		});

		final FoeActor foe1Actor = wrapper.stage.findFoe(foe1);
		Timeline.ActionTimelineItem[] items = wrapper.stage.timeline.findAllActionItems();
		assertEquals(1, items.length);
		assertTrue(items[0].action.isForAttack());
		assertEquals(foe1, items[0].action.asAttack().foe.foe);
		assertEquals(chargingMove, items[0].action.asAttack().move.move);
		assertNotNull(items[0].action.asAttack().move.parent);
		assertEquals(MoveTemplate.SUB_TITLE_RELEASE, items[0].action.asAttack().move.title);

		wrapper.runUntil(ActionFinishedMessage.class);
		float originReleaseTime = items[0].time;
		float nextTurnTime = wrapper.stage.now() + foe1Actor.speedRemains;

		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);
		assertTrue(wrapper.stage.now() < originReleaseTime);
		wrapper.onActionRequest(wrapper.createDefaultController().withMove(speedUpMove), foe2);
		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertEquals(1, message.items.size());
			assertEquals(BStat.SP, message.items.get(0).bStat);
			assertEquals(2, message.items.get(0).level);
			return true;
		});
		wrapper.runUntil(TimelineOrderChangeMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			return true;
		});

		items = wrapper.stage.timeline.findAllActionItems();
		assertEquals(1, items.length);
		assertTrue(items[0].action.isForAttack());
		assertEquals(foe1, items[0].action.asAttack().foe.foe);
		assertEquals(chargingMove, items[0].action.asAttack().move.move);
		assertNotNull(items[0].action.asAttack().move.parent);
		assertEquals(MoveTemplate.SUB_TITLE_RELEASE, items[0].action.asAttack().move.title);

		assertTrue(items[0].time < originReleaseTime);
		assertTrue(wrapper.stage.now() + foe1Actor.speedRemains < nextTurnTime);
		wrapper.runUntil(ActionFinishedMessage.class);
	}

	@Test
	public void burnAppliedWhenCharging() {
		MoveTemplate burnMove = storage.findMoveById(10000013, 1);
		assertEquals("肯定烧伤", burnMove.name);
		MoveTemplate chargingMove = storage.findMoveById(10000150, 1);
		assertEquals("蓄力攻击", chargingMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(123))
				.withLevel(30)
				.withMove(burnMove)
				.skipShuffleVStat()
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(114))
				.withLevel(30)
				.withMove(chargingMove)
				.skipShuffleVStat()
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.BUFF_BURN));
			assertTrue(message.success);
			return true;
		});

		System.out.println("Test 1: In period of charging, burn buff should not cause damage");
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);
		wrapper.onMessage(FoeReceiveDamageMessage.class, message -> {
			throw new RuntimeException("In period of charging, burn buff should not cause damage.");
		});

		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.BUFF_CHARGING));
			assertTrue(message.success);
			return true;
		});
		wrapper.runUntil(TurnFinishedMessage.class);

		System.out.println("Test 2: In period of release, burn buff should cause damage");
		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(ActionStartMessage.class, message -> {
			final AttackAction action = message.action.asAttack();
			return action.foe.foe == foe2 && action.move.hasTag(StageConstant.TAG_CHARGING);
		});
		wrapper.runUntil(ActionFinishedMessage.class);
		final float now = wrapper.now();

		wrapper.runUntil(ActionStartMessage.class, message -> {
			final BuffAction action = message.action.asBuff();
			assertTrue(action.buff.is(StageConstant.BUFF_BURN));
			assertEquals(0, now, wrapper.now());
			return true;
		});
		wrapper.runUntil(FoeReceiveDamageMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertEquals(StageConstant.EFFECT_BURN, message.title);
			return true;
		});
	}
}
